// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_engine.h"
#include "mk_error.h"
#include "mk_di8_keyboard.h"

namespace MK3DE
{
	CDeviceKeyboard::CDeviceKeyboard(void) : 
	_keyboard(NULL), 
	_isDeviceLost(FALSE), 
	_bufferSize(0),
	_eventBuffer(NULL),
	_eventCount(0),
	_currentEvent(0)
	{
	}

	CDeviceKeyboard::~CDeviceKeyboard(void)
	{
		SAFE_RELEASE(_keyboard);
		delete []_eventBuffer;
	}

	HRESULT CDeviceKeyboard::UpdateImmediateData(BYTE state[], DWORD size)
	{
		if (!_keyboard)
			return E_FAIL;

		HRESULT hr = E_FAIL;
		if (_isDeviceLost)
		{
			hr = _keyboard->Acquire();
			if (FAILED(hr))
				return hr;
			_isDeviceLost = FALSE;
		}

		ZeroMemory(state, sizeof(BYTE) * size);
		hr = _keyboard->GetDeviceState(sizeof(BYTE) * size, state);
		if (FAILED(hr)) 
		{
			hr = _keyboard->Acquire();
			if (hr == DIERR_INPUTLOST)
				_isDeviceLost = TRUE;

			return hr;
		}

		return S_OK;
	}

	HRESULT CDeviceKeyboard::UpdateBufferedData()
	{
		if (!_keyboard) 
			return E_FAIL;

		HRESULT hr = E_FAIL;
		if (_isDeviceLost)
		{
			hr = _keyboard->Acquire();
			if (FAILED(hr))
				return hr;
			_isDeviceLost = FALSE;
		}

		DWORD dwElements = _bufferSize;
		hr = _keyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), _eventBuffer, &dwElements, 0);
		if (hr != DI_OK)
		{
			// We got an error or we got DI_BUFFEROVERFLOW.
			//
			// If we want to be cleverer, we could do a
			// GetDeviceState() and compare the current state
			// against the state we think the device is in,
			// and process all the states that are currently
			// different from our private state.
			if (hr == DI_BUFFEROVERFLOW)
				return hr;

			hr = _keyboard->Acquire();
			if (hr == DIERR_INPUTLOST)
				_isDeviceLost = TRUE;

			return hr;
		}

		_currentEvent = 0;
		_eventCount = dwElements;

		return S_OK;
	}

	HRESULT CDeviceKeyboard::ClearBuffer()
	{
		_eventCount = 0;
		_currentEvent = 0;
		return S_OK;
	}

	HRESULT CDeviceKeyboard::GetKeyState(BYTE keyState[], DWORD key, KEY_STATE& state)
	{
		if (!_keyboard)
			return E_FAIL;

		if (keyState[key] & 0x80)
			state = KS_PRESSED;
		else
			state = KS_UNPRESSED;

		return S_OK;
	}

	HRESULT CDeviceKeyboard::GetInputEvent(InputEvent& event)
	{
		if (_currentEvent >= _eventCount)
			return E_FAIL;

		DWORD key = _eventBuffer[_currentEvent].dwOfs;
		KEY_STATE state = KS_UNPRESSED;
		if (0x80 & _eventBuffer[_currentEvent].dwData)
			state = KS_PRESSED;

		_currentEvent++;
		event.type = IET_KEY;
		event.key = key;
		event.state = state;
		return S_OK;
	}

	HRESULT CDeviceKeyboard::Create(LPDIRECTINPUT8 pDirectInput, HWND hWnd, COOPERATIVE_LEVEL cl, DWORD size)
	{
		HRESULT hr = E_FAIL;
		DIPROPDWORD dipdw;
		DWORD dwCoopFlags = 0;

		hr = pDirectInput->CreateDevice(GUID_SysKeyboard, &_keyboard, NULL);
		MK_PROCESS_COM_ERROR(hr);

		hr = _keyboard->SetDataFormat(&c_dfDIKeyboard);
		MK_PROCESS_COM_ERROR(hr);

		switch (cl)
		{
		case CL_EXC_FORE:
			dwCoopFlags = DISCL_EXCLUSIVE | DISCL_FOREGROUND;
			break;

		case CL_EXC_BACK:
			dwCoopFlags = DISCL_EXCLUSIVE | DISCL_BACKGROUND;
			break;

		case CL_NON_EXC_FORE:
			dwCoopFlags = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND;
			break;

		case CL_NON_EXC_BACK:
			dwCoopFlags = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
			break;

		default:
			hr = E_INVALIDARG;
			goto Exit0;
		}

		hr = _keyboard->SetCooperativeLevel(hWnd, dwCoopFlags);
		MK_PROCESS_COM_ERROR(hr);

		dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
		dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
		dipdw.diph.dwObj        = 0;
		dipdw.diph.dwHow        = DIPH_DEVICE;
		dipdw.dwData            = size;

		hr = _keyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
		MK_PROCESS_COM_ERROR(hr);

		hr = _keyboard->Acquire();
		if (hr == DIERR_INPUTLOST)
			_isDeviceLost = TRUE;
		else
			_isDeviceLost = FALSE;

		_eventBuffer = new DIDEVICEOBJECTDATA[size];
		MK_PROCESS_ERROR(_eventBuffer);

		_bufferSize = size;
		_enable = TRUE;
		hr = S_OK;

	Exit0:
		if (FAILED(hr))
		{
			SAFE_RELEASE(_keyboard);
		}

		return hr;
	}
}